using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using Chromos.Actors;
using ShaderProject;
using Chromos.AIEngine;
using Chromos.PhysicsEngine;

namespace Chromos
{
	/// <summary>
	/// This is the main type for your game
	/// </summary>
	/// 

	public class Game1 : Microsoft.Xna.Framework.Game
	{
		#region Game variables
		GraphicsDeviceManager graphics;
		SpriteBatch spriteBatch;
		Texture2D[] playerMovingAnimation = new Texture2D[20];
		Texture2D[] Level = new Texture2D[10];
		Texture2D[] playerTempDead = new Texture2D[10];
		List<Texture2D> playerDeadTexture = new List<Texture2D>();
		public List<Enemy> enemies;
		public List<Projectile> projectiles;
		Vector2 playerStartingPosition = Vector2.Zero;
		Vector2 pos2 = Vector2.Zero;
		Vector2 returnPos = Vector2.Zero;
		Vector2 lastPos = Vector2.Zero;
        const float jumpingSpeed = 10f;
		string readerbg = "../../../MapData/001bg.txt";

		int elapsedGameTime = 0;
		public int timeSinceLastUpdate = 0;

		int level;
		string reader;
		List<char[]> map;
		List<char[]> mapbg;
		public Player player;
		float prevPlayerX = 0;
		float prevPlayerY = 0;
		public List<Sprite.Axis> mapSprites;
		public List<Sprite.Axis> bgSprites;
		List<Sprite.Axis> Shots = new List<Sprite.Axis>();
		Physics physics;
		int inputTimer = 0;
		int INPUTWAIT = 3;
		#endregion
		#region Controller variables
		Controller controller;
		List<Controller.Action> actionList;
		#endregion

		public Game1()
		{
			graphics = new GraphicsDeviceManager(this);
			Content.RootDirectory = "Content";

			graphics.PreferredBackBufferWidth = 1280;
			graphics.PreferredBackBufferHeight = 768;
			graphics.ApplyChanges();
		}

		/// <summary>
		/// Allows the game to perform any initialization it needs to before starting to run.
		/// This is where it can query for any required services and load any non-graphic
		/// related content.  Calling base.Initialize will enumerate through any components
		/// and initialize them as well.
		/// </summary>
		protected override void Initialize()
		{
			// Controller
			actionList = new List<Controller.Action>();
			actionList.Add(new Controller.Action("LUp", Buttons.LeftThumbstickUp, Keys.W));
			actionList.Add(new Controller.Action("LDown", Buttons.LeftThumbstickDown, Keys.S));
			actionList.Add(new Controller.Action("LLeft", Buttons.LeftThumbstickLeft, Keys.A));
			actionList.Add(new Controller.Action("LRight", Buttons.LeftThumbstickRight, Keys.D));
			actionList.Add(new Controller.Action("RUp", Buttons.RightThumbstickUp, Keys.Up));
			actionList.Add(new Controller.Action("RDown", Buttons.RightThumbstickDown, Keys.Down));
			actionList.Add(new Controller.Action("RLeft", Buttons.RightThumbstickRight, Keys.Left));
			actionList.Add(new Controller.Action("RRight", Buttons.RightThumbstickRight, Keys.Right));
			actionList.Add(new Controller.Action("LT", Buttons.LeftTrigger, Keys.Q));
			actionList.Add(new Controller.Action("RT", Buttons.RightTrigger, Keys.Space));
			actionList.Add(new Controller.Action("A", Buttons.A, Keys.C));
			actionList.Add(new Controller.Action("B", Buttons.B, Keys.V));
			actionList.Add(new Controller.Action("Start", Buttons.Start, Keys.L));
			actionList.Add(new Controller.Action("DPadR", Buttons.DPadRight, Keys.M));
			controller = new Controller(PlayerIndex.One, actionList);

			// Graphics
			GraphicsDevice.RenderState.AlphaBlendEnable = true;
			GraphicsDevice.RenderState.SourceBlend = Blend.One;
			GraphicsDevice.RenderState.DestinationBlend = Blend.One;

			// Physics
			physics = new Physics(this);

			// Background
			InitLevelBG();

			// Level
			InitLevel();

			// Actors
			InitActors();

			level = 1;



            base.Initialize();
		}
		protected void InitActors()
		{
			enemies = new List<Enemy>();
			projectiles = new List<Projectile>();
			int tileWidth = 30;
			int tileHeight = 30;
			int x = 0;
			int y = 0;
			Vector2 velocity = new Vector2(0, 0);
			foreach (char[] row in map)
			{
				foreach (char tileType in row)
				{
					Vector2 pos = new Vector2(x, y);
					if (tileType == 'f')
					{
						FlyFish flyFish = new FlyFish(this, pos, velocity, Content, false, "normal");
						flyFish.load();
						enemies.Add(flyFish);
					}
					if (tileType == 'e')
					{
						ExplosiveBug explBug = new ExplosiveBug(this, pos, velocity, Content, false, "normal");
						explBug.load();
						enemies.Add(explBug);
					}
					if (tileType == 's')
					{
						SnowMan snowMan = new SnowMan(this, pos, velocity, Content, false, "normal");
						snowMan.load();
						enemies.Add(snowMan);
					}
					if (tileType == 'P')
					{
						Plant plant = new Plant(this, pos, velocity, Content, false, "normal");
						plant.load();
						enemies.Add(plant);
					}
					if (tileType == 'B')
					{
						Bone bone = new Bone(this, pos, velocity, Content, false, "normal");
						bone.load();
						enemies.Add(bone);
					}
					if (tileType == 'F')
					{
						FireMonster fireMonster = new FireMonster(this, pos, velocity, Content, false, "normal");
						fireMonster.load();
						enemies.Add(fireMonster);
					}
					if (tileType == 'r')
					{
						RockMan rockMan = new RockMan(this, pos, velocity, Content, false, "normal");
						rockMan.load();
						enemies.Add(rockMan);
					}
					if (tileType == '0')
					{
						RedBoss redBoss = new RedBoss(this, pos, velocity, Content, false, "normal");
						redBoss.load();
						enemies.Add(redBoss);
					}
					if (tileType == 'p')
					{
						pos.Y -= 1;
						playerStartingPosition = pos;
						player = new Player(this, playerStartingPosition, Vector2.Zero, Content, false);
						prevPlayerX = player.position.X;
						prevPlayerY = player.position.Y;
						player.load();
					}
					x += tileWidth;
				}
				x = 0;
				y += tileHeight;
			}
		}
		protected void InitLevel()
		{
			// Pick the level text
			switch (level)
			{
				//This switches levels. The levels must be named like so, and be placed in a folder named MapData
				case 1: reader = "../../../MapData/001.txt"; break;
				case 2: reader = "../../../MapData/001.txt"; break;
				case 3: reader = "../../../MapData/001.txt"; break;
				default: reader = "../../../MapData/001.txt"; break;
			}

			Level[0] = Content.Load<Texture2D>(@"Sprites/Brick");
			Level[1] = Content.Load<Texture2D>(@"Sprites/LEBrick");
			Level[2] = Content.Load<Texture2D>(@"Sprites/REBrick");

			// Init
			Map.MapReader mapReader = new Map.MapReader(reader);
			map = mapReader.GetMapData();
			if (mapSprites != null)
			{
				mapSprites.Clear();
			}
			mapSprites = new List<Sprite.Axis>();

			int tileWidth = 30;
			int tileHeight = 30;
			int x = 0;
			int y = 0;
			Texture2D sprite;
			Vector2 pos = new Vector2(x, y);

			// Iterate over the file, generating the objects.
			foreach (char[] rows in map)
			{
				foreach (char tileType in rows)
				{
					pos.X = x;
					pos.Y = y;
					if (tileType == 'b')
					{
						sprite = Level[0];
						mapSprites.Add(new Sprite.Axis((int)pos.X, (int)pos.Y, tileWidth, tileHeight, 0, sprite));
					}
					x += tileWidth;
				}
				x = 0;
				y += tileHeight;
			}
		}

		/// <summary>
		/// LoadContent will be called once per game and is the place to load
		/// all of your content.
		/// </summary>
		/// 
		protected override void LoadContent()
		{
			// Create a new SpriteBatch, which can be used to draw textures.
			spriteBatch = new SpriteBatch(GraphicsDevice);
			LoadSprites();
		}
		protected void LoadSprites()
		{
		}
		/// <summary>
		/// UnloadContent will be called once per game and is the place to unload
		/// all content.
		/// </summary>
		protected override void UnloadContent()
		{
			// TODO: Unload any non ContentManager content here
		}
		
		/// <summary>
		/// Allows the game to run logic such as updating the world,
		/// checking for collisions, gathering input, and playing audio.
		/// </summary>
		/// <param name="gameTime">Provides a snapshot of timing values.</param>
		protected override void Update(GameTime gameTime)
		{
			timeSinceLastUpdate = (int)gameTime.ElapsedGameTime.TotalMilliseconds;

			// Allows the game to exit
			if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
				this.Exit();

			controller.update();

			HandleInput();
			UpdateProjectiles();
			UpdatePlayer();
			UpdateEnemies();
			UpdateLevel();

			physics.update();

			base.Update(gameTime);
		}
		protected void HandleInput()
		{
			inputTimer += timeSinceLastUpdate;
			if (inputTimer > INPUTWAIT)
			{
				inputTimer = 0;
				if (!player.getDead())
				{
					if (controller.isActionPressed("DPadR"))
					{
						((PlayerAIEngine)player.getAIEngine()).controllerSwitchWeapons();
					}
					if (controller.isActionPressed("LLeft"))
					{
						((PlayerAIEngine)player.getAIEngine()).controllerMoveLeft();
						//collision.lastDirection = 1;
					}
					else if (controller.isActionPressed("LRight"))
					{
						((PlayerAIEngine)player.getAIEngine()).controllerMoveRight();
						//collision.lastDirection = 2;
					}
					else if (controller.isActionPressed("LDown"))
					{
						//collision.lastDirection = 0;
					}
					else
					{
						((PlayerAIEngine)player.getAIEngine()).controllerIdle();
					}

					// If you press up and you aren't already jumping and you're on the floor...
					if (controller.isActionPressed("LUp"))
					{
						((PlayerAIEngine)player.getAIEngine()).controllerUp();
					}
					//If you pressed the right trigger, shoot what you have in your inventory
					if (controller.isActionPressed("RT"))
					{
						((PlayerAIEngine)player.getAIEngine()).controllerShoot();
					}
				}
			}
			if ((controller.isActionPressed("Start")))
			{
				level++;
				player.setAlive();
			}
		}
		protected void UpdateProjectiles()
		{
			for (int i = 0; i < projectiles.Count; i++)
			{
				if (!projectiles.ElementAt(i).isLoaded())
					projectiles.ElementAt(i).load();
				if (projectiles.ElementAt(i).isFinished())
				{
					projectiles.RemoveAt(i);
					i--;
					continue;
				}
				projectiles.ElementAt(i).update();
			}
		}
		protected void UpdatePlayer()
		{
			player.update();
		}
		protected void UpdateEnemies()
		{
			foreach (Enemy enemy in enemies)
			{
				enemy.update();
			}
		}
		protected void UpdateLevel()
		{
			//These lines put the bricks that are made by the player into a temporary list
			//to be saved when the map is redrawn

			int x = 0;
			int y = 0;
			Vector2 pos = new Vector2(x, y);

			//if (mapJump < 0) { mapJump = 0; mapSlide = 0; }
			/*foreach (char[] rows in map)
			{
				mapSize = 0;
				foreach (char tileType in rows)
				{
					if (i >= mapJump)
					{
						pos.X = x;
						pos.Y = y;

						if (tileType == 'f')
						{
							sprite = Level[0];
							mapSprites.Add(new Sprite.Axis((int)pos.X, (int)pos.Y, tileWidth, tileHeight, 0, sprite));
						}
						x += tileWidth;
					}
					mapSize++;
					i++;
				}
				x = mapSlide;
				y += tileHeight;
				i = 0;
			}*/
			//collision.Obs = Obs;
		}

        private float xTranslation = 0;
        private float rightBound = .7f;
        private float leftBound = .3f;
        /* if past rightBound * screen width and moving right
	     *    translate screen to the left
         * if past leftBound * screen width and moving left
	     *    translate screen to the right */
        private Matrix GetTranslationMatrix()
        {
            int timeDelta = this.timeSinceLastUpdate;
            Matrix translation = Matrix.CreateTranslation(xTranslation, 0, 0);
            // 0: start, 1: second, ...
            int screenIndex = (int)player.position.X / (int)graphics.PreferredBackBufferWidth;
            float playerScreenX = player.position.X + xTranslation;
            if (playerScreenX < graphics.PreferredBackBufferWidth * leftBound && player.velocity.X < 0 && (xTranslation < -.0001))
            {
                xTranslation += -player.velocity.X * ((float)timeDelta / 1000);
                translation = Matrix.CreateTranslation(xTranslation, 0, 0);
            }
            else if (playerScreenX > graphics.PreferredBackBufferWidth * rightBound && player.velocity.X > 0)
            {
                xTranslation -= player.velocity.X * ((float)timeDelta / 1000);
                translation = Matrix.CreateTranslation(xTranslation, 0, 0);
            }
            return translation;
        }

		// put anywhere in game1.cs
		protected void InitLevelBG()
        {
            // Pick the level text
            switch (level)
            {
                //This switches levels. The levels must be named like so, and be placed in a folder named MapData
                case 1: readerbg = "../../../MapData/001bg.txt"; break;
                case 2: readerbg = "../../../MapData/001bg.txt"; break;
                case 3: readerbg = "../../../MapData/001bg.txt"; break;
                default: readerbg = "../../../MapData/001bg.txt"; break;
            }

            Level[0] = Content.Load<Texture2D>(@"Sprites/Scenery/tree1");
            Level[1] = Content.Load<Texture2D>(@"Sprites/Scenery/tree2");
            Level[2] = Content.Load<Texture2D>(@"Sprites/Scenery/sky1");

            // Init
            Map.MapReader mapReader = new Map.MapReader(readerbg);
            mapbg = mapReader.GetMapData();
            if (bgSprites != null)
            {
                bgSprites.Clear();
            }
            bgSprites = new List<Sprite.Axis>();

            int tileWidth = 30;
            int tileHeight = 30;
            int x = 0;
            int y = 0;
            Texture2D sprite;
            Vector2 pos = new Vector2(x, y);

            // Iterate over the file, generating the objects.
            foreach (char[] rows in mapbg)
            {
                foreach (char tileType in rows)
                {
                    pos.X = x;
                    pos.Y = y;
                    if (tileType == '1')
                    {
                        sprite = Level[0];
                        bgSprites.Add(new Sprite.Axis((int)pos.X, (int)pos.Y + tileHeight - Level[0].Height, tileWidth, Level[0].Height, 0, sprite));
                    }
                    else if (tileType == '2')
                    {
                        sprite = Level[1];
                        bgSprites.Add(new Sprite.Axis((int)pos.X, (int)pos.Y + tileHeight - Level[1].Height, tileWidth, Level[0].Height, 0, sprite));
                    }
                    else if (tileType == 's')
                    {
                        sprite = Level[2];
                        bgSprites.Add(new Sprite.Axis((int)pos.X, (int)pos.Y, tileWidth, Level[0].Height, 0, sprite));
                    }
                    x += tileWidth;
                }
                x = 0;
                y += tileHeight;
            }
        }


		/// <summary>
		/// This is called when the game should draw itself.
		/// </summary>
		/// <param name="gameTime">Provides a snapshot of timing values.</param>
		protected override void Draw(GameTime gameTime)
		{
			GraphicsDevice.Clear(Color.White);

            Matrix translation = GetTranslationMatrix();

            spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None, translation);

			elapsedGameTime = (int)gameTime.TotalGameTime.TotalMilliseconds;

			DrawFloor();

			spriteBatch.Draw(player.getCurrentImage(), player.position, Color.White);
			foreach (Enemy enemy in enemies)
			{
				spriteBatch.Draw(enemy.getCurrentImage(), enemy.position, null, Color.White, 0, Vector2.Zero, 1, SpriteEffects.None, 0);
			}
			foreach (Projectile projectile in projectiles)
			{
				if (!projectile.isLoaded())
					projectile.load();
				spriteBatch.Draw(projectile.getCurrentImage(), projectile.position, null, Color.White, 0, Vector2.Zero, 1, SpriteEffects.None, 0);
			}
			
			spriteBatch.End();

			base.Draw(gameTime);
		}
		protected void DrawFloor()
		{
			foreach (Sprite.Axis Objects in bgSprites)
			{
				spriteBatch.Draw(Objects.sprite, Objects.returnVect(), null, Color.White, 0, Vector2.Zero, 1, SpriteEffects.None, 0);
			}
			foreach (Sprite.Axis Objects in mapSprites)
			{
				spriteBatch.Draw(Objects.sprite, Objects.returnVect(), null, Color.White, 0, Vector2.Zero, 1, SpriteEffects.None, 0);
			}
		}
	}
}